//
// Created by Jinyu Zhu on 2022/4/18.
//

#include "outer.h"

FOut::FOut(const std::string &OutFilePath): Buffer(nullptr), BufferLen(0), Cs() {
    Buffer = new char[MaxBfSize];
    if (OutFilePath == "CON") {
        FileId = stdout;
    } else {
        FileId = fopen(OutFilePath.c_str(), "wb+");
        if (FileId == nullptr) throw std::runtime_error("file cannot open");
    }
}

FOut::~FOut() {
    if (FileId != nullptr) {
        FlushBf();
    }
    delete[] Buffer;
    if (FileId != nullptr) {
        if (fclose(FileId) != 0) {
            // close error
        }
    }
}

void FOut::FlushBf() {
    if (fwrite(Buffer, 1, BufferLen, FileId) != BufferLen) {
        throw std::runtime_error("Error writing to file.");
    }
    BufferLen = 0;
}

int FOut::PutCh(const char &Ch) {
    if (BufferLen == MaxBfSize) { FlushBf(); }
    return Buffer[BufferLen++] = Ch;
}

int FOut::PutBf(const void *SrcBf, const int &SrcBfL) {
    int SBfS = 0;
    if (BufferLen + SrcBfL > MaxBfSize) {
        for (int c = 0; c < SrcBfL; c++) {
            SBfS += PutCh(((char*) SrcBf)[c]);
        }
    } else {
        for (int c = 0; c < SrcBfL; c++) {
            SBfS += (Buffer[BufferLen++]=((char*)SrcBf)[c]);
        }
    }
    return SBfS;
}

void FOut::Save(const std::vector<int> &VInt) {
    int size = (int) VInt.size();
    Save(size);
    for (const auto & iter : VInt) {
        Save(iter);
    }
}

void FOut::Save(const std::vector<std::string> &VStr) {
    int size = (int) VStr.size();
    Save(size);
    for (const auto & iter : VStr) {
        Save(iter);
    }
}

void FOut::Save(const std::map<std::string, int>& SIMap) {
    int size = (int) SIMap.size();
    Save(size);
    for (const auto & iter : SIMap) {
        Save(iter.first);
        Save(iter.second);
    }
}

void FOut::Save(const std::map<std::string, std::vector<int> > &SVMap) {
    int size = (int) SVMap.size();
    Save(size);
    for (const auto & iter : SVMap) {
        Save(iter.first);
        Save(iter.second);
    }
}

FIn::FIn(const std::string &InFilePath): Buffer(nullptr), BfL(0), BfC(0), FileId(nullptr), Cs() {
    FileId = fopen(InFilePath.c_str(), "rb");
    if (FileId == nullptr) throw std::runtime_error("file cannot open");
    Buffer = new char [MaxBfSize];
    BfC = BfL = -1;
    FillBf();
}

FIn::~FIn() {
    if (FileId != nullptr) {
        if (fclose(FileId) != 0) {
            // close error
        }
    }
    delete[] Buffer;
}

void FIn::FillBf() {
    if (! ((BfC == BfL) && ((BfL == -1) || (BfL == MaxBfSize))) ) {
        throw std::runtime_error("Error reading file.");
    }
    BfL = int(fread(Buffer, 1, MaxBfSize, FileId));
    if (! ((BfC != 0) || (BfL != 0)) ) {
        throw std::runtime_error("Error reading file.");
    }
    BfC = 0;
}

int FIn::GetBf(const void *LBf, const int &LBfL) {
    int LBfS = 0;
    if (BfC + LBfL > BfL) {
        for (int LBfC = 0; LBfC < LBfL; LBfC++) {
            if (BfC == BfL) { FillBf(); }
            LBfS += (((char *)LBf)[LBfC] = Buffer[BfC++]);
        }
    } else {
        for (int LBfC = 0; LBfC < LBfL; LBfC++) {
            LBfS += (((char *)LBf)[LBfC] = Buffer[BfC++]);
        }
    }

    return LBfS;
}

void FIn::Load(char*& CStr) {
    char Ch;
    Load(Ch);
    int StrLen = int(Ch);
    if (StrLen < 0) throw std::runtime_error("error reading stream string");
    CStr = new char [StrLen + 1];
    if (StrLen > 0) { Cs += GetBf(CStr, StrLen); }
    CStr[StrLen] = char(0);
}

std::string FIn::LoadStr() {
    char* CStr; Load(CStr);
    std::string res(CStr);
    delete[] CStr;
    return res;
}

void FIn::Load(std::vector<int> &VInt) {
    if (!VInt.empty()) throw std::runtime_error("load to non-empty vector");
    int VSize; Load(VSize);
    for (int i = 0; i < VSize; i++) {
        int cur; Load(cur);
        VInt.push_back(cur);
    }
}

void FIn::Load(std::vector<std::string> &VStr) {
    if (!VStr.empty()) throw std::runtime_error("load to non-empty vector");
    int VSize; Load(VSize);
    for (int i = 0; i < VSize; i++) {
        VStr.push_back(LoadStr());
    }
}

void FIn::Load(std::map<std::string, int> &SIMap) {
    if (!SIMap.empty()) throw std::runtime_error("load to non-empty map");
    int MSize; Load(MSize);
    for (int i = 0; i < MSize; i++) {
        std::string key = LoadStr();
        int val; Load(val);
        SIMap.insert({ key, val });
    }
}

void FIn::Load(std::map<std::string, std::vector<int> >& SVMap) {
    if (!SVMap.empty()) throw std::runtime_error("load to non-empty map");
    int MSize; Load(MSize);
    for (int i = 0; i < MSize; i++) {
        std::string key = LoadStr();
        std::vector<int> val; Load(val);
        SVMap.insert({ key, val });
    }

}


